#pragma once

#include <boost/optional.hpp>
#include <boost/thread.hpp>
#include <boost/signals2.hpp>
#include <boostext/signal.hpp>


 

namespace services
{
	/**
	 * This class is an implementation of the future component, described in the article
	 * "Transporting Values and Exceptions between Threads". It can be found at:
	 * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/h2096.html
	 *
	 * A future is DefaultConstructible, CopyConstructible and Assignable. It has reference semantics; all copies of the
	 * same future are equivalent and interchangable. All operations on a future exception assignment are strongly thread safe or
	 * sequentially consistent, that is, the behavior of concurrent calls is as if the calls have been issued sequentially in an
	 * unspecified order.
	 */
	template <typename T>
	class future
	{
	public:

		/// This signal is used to inform anyone that the future is ready
		typedef boost::signals2::signal<void()>   notify;



		/**
		 * Creates an empty future.
		 */
		future()
			: m_storage(new storage)
		{}

		/**
		 * Tests if this future is ready or not.
		 * It is ready when it contains a value OR an exception.
		 */
		bool isReady() const
		{
			read_lock lock(m_storage->m_mutex);

			// Funfact #1: std::exception_ptr doesn't provide operator !=
			return m_storage->m_value || !(m_storage->m_exception == nullptr);
		}

		/**
		 * Tests if this future holds a value or not.
		 */
		bool has_value() const
		{
			read_lock lock(m_storage->m_mutex);
			return m_storage->m_value;
		}

		/**
		 * Tests if this future holds an exception or not.
		 */
		bool has_exception() const
		{
			read_lock lock(m_storage->m_mutex);

			// Funfact #1: std::exception_ptr doesn't provide operator !=
			return !(m_storage->m_exception == nullptr);
		}

		/**
		 * Blocks until this future is ready and then either throws or returns the value.
		 *
		 * @returns   the value stored in this future (when present)
		 * @throws    the exception stored in this future (when present)
		 */
		const T& operator()() const
		{
			wait();

			read_lock lock(m_storage->m_mutex);

			// Funfact #1: std::exception_ptr doesn't provide operator !=
			if(!(m_storage->m_exception == nullptr))
				rethrow_exception(m_storage->m_exception);

			return *m_storage->m_value;
		}

		/**
		 * Blocks until this future is ready.
		 */
		void wait() const
		{
			while(!isReady())
			{
				boost::this_thread::yield();
			}
		}

		/**
		 * Blocks until this future is ready or until the given duration has been waited.
		 *
		 * @returns   true in case a value or an exception has been set
		 * @see       ready
		 */
		bool timed_wait(boost::posix_time::time_duration d)
		{
			boost::posix_time::ptime end_wait = boost::posix_time::microsec_clock::local_time() + d;
			while(!isReady())
			{
				if(boost::posix_time::microsec_clock::local_time() >= end_wait)
					return false;

				boost::this_thread::yield();
			}

			return true;
		}



		/**
		 * Sets the future's value to the given one.
		 */
		void set_value(T value)
		{
			{
				write_lock lock(m_storage->m_mutex);
				m_storage->m_value = value;
			}

			// This future is ready, therefore we emit the signal.
			// This MUST be done outside of the lock, otherwise
			// no one can use the future inside a slot, connected to this signal.
			m_storage->m_ready();
		}

		/**
		 * Sets the future's exception to the given one.
		 */
		void set_exception(std::exception_ptr except)
		{
			{
				write_lock lock(m_storage->m_mutex);
				m_storage->m_exception = except;
			}

			// This future is ready, therefore we emit the signal.
			// This MUST be done outside of the lock, otherwise
			// no one can use the future inside a slot, connected to this signal.
			m_storage->m_ready();
		}



		/**
		 * @returns the ready signal
		 */
		notify& ready()
		{
			return m_storage->m_ready;
		}

	private:

		typedef boost::shared_mutex         mutex;
		typedef boost::shared_lock<mutex>   read_lock;
		typedef boost::unique_lock<mutex>   write_lock;

		struct storage
		{
			boost::optional<T>   m_value;
			std::exception_ptr   m_exception;
			mutex                m_mutex;
			notify               m_ready;
		};

		boost::shared_ptr<storage>   m_storage;
	};
	///////////////////////////////////////////////////////////////////////////////////////////////

	template <>
	class future<void>
	{
	public:

		/// This signal is used to inform anyone that the future is ready
		typedef boost::signals2::signal<void()>   notify;

		/**
		 * Creates an empty future.
		 */
		future()
			: m_storage(new storage)
		{}

		/**
		 * Tests if this future is ready or not.
		 * It is ready when it contains a value OR an exception.
		 */
		bool isReady() const
		{
			read_lock lock(m_storage->m_mutex);

			// Funfact #1: std::exception_ptr doesn't provide operator !=
			return m_storage->m_value || !(m_storage->m_exception == nullptr);
		}

		/**
		 * Tests if this future holds a value or not.
		 */
		bool has_value() const
		{
			read_lock lock(m_storage->m_mutex);

			return m_storage->m_value == true;
		}

		/**
		 * Tests if this future holds an exception or not.
		 */
		bool has_exception() const
		{
			read_lock lock(m_storage->m_mutex);

			// Funfact #1: std::exception_ptr doesn't provide operator !=
			return !(m_storage->m_exception == nullptr);
		}

		/**
		 * Blocks until this future is ready and then throws if an exception has been set.
		 *
		 * @throws    the exception stored in this future (when present)
		 */
		void operator()() const
		{
			wait();

			read_lock lock(m_storage->m_mutex);

			// Funfact #1: std::exception_ptr doesn't provide operator !=
			if(!(m_storage->m_exception == nullptr))
				rethrow_exception(m_storage->m_exception);
		}

		/**
		 * Blocks until this future is ready.
		 */
		void wait() const
		{
			while(!isReady())
			{
				boost::this_thread::yield();
			}
		}

		/**
		 * Blocks until this future is ready or until the given duration has been waited.
		 *
		 * @returns   true in case a value or an exception has been set
		 * @see       ready
		 */
		bool timed_wait(boost::posix_time::time_duration d)
		{
			boost::posix_time::ptime end_wait = boost::posix_time::microsec_clock::local_time() + d;
			while(!isReady())
			{
				if(boost::posix_time::microsec_clock::local_time() >= end_wait)
					return false;

				boost::this_thread::yield();
			}

			return true;
		}



		/**
		 * Sets the future's value to the given one.
		 */
		void set_value()
		{
			{
				write_lock lock(m_storage->m_mutex);
				m_storage->m_value = true;
			}

			// This future is ready, therefore we emit the signal.
			// This MUST be done outside of the lock, otherwise
			// no one can use the future inside a slot, connected to this signal.
			m_storage->m_ready();
		}

		/**
		 * Sets the future's exception to the given one.
		 */
		void set_exception(std::exception_ptr except)
		{
			{
				write_lock lock(m_storage->m_mutex);
				m_storage->m_exception = except;
			}

			// This future is ready, therefore we emit the signal.
			// This MUST be done outside of the lock, otherwise
			// no one can use the future inside a slot, connected to this signal.
			m_storage->m_ready();
		}



		/**
		 * @returns the ready signal
		 */
		notify& ready()
		{
			return m_storage->m_ready;
		}

	private:

		typedef boost::shared_mutex         mutex;
		typedef boost::shared_lock<mutex>   read_lock;
		typedef boost::unique_lock<mutex>   write_lock;

		struct storage
		{
			bool                 m_value;
			std::exception_ptr   m_exception;
			mutex                m_mutex;
			notify               m_ready;
		};

		boost::shared_ptr<storage>   m_storage;
	};
	///////////////////////////////////////////////////////////////////////////////////////////////




	/**
	 * This component shall transfer the future principle to a sequence that
	 * stores multiple values and multiple exceptions, thus allowing a future to be invoked
	 * multiple times without losing data.
	 *
	 * Unspecified until a good design can be found.
	 */
	template <typename T>
	class future_sequence;




	/**
	 * This component shall transfer the future principle to a sequence that
	 * stores multiple exceptions, thus allowing a future to be invoked
	 * multiple times without losing data.
	 */
	template <>
	class future_sequence<void>
	{
	public:

		future_sequence()
			: m_storage(new storage)
		{}

		/**
		 * Blocks until this future is ready and then throws if an exception has been added.
		 * Removes the exception from the list of exceptions.
		 *
		 * @throws    the exception stored in this future (when present)
		 */
		void operator()() const
		{
			wait();

			write_lock lock(m_storage->m_mutex);

			// Pop the first exception from the list and rethrow it
			if(m_storage->m_exceptions.size() > 0)
			{
				std::exception_ptr exception = m_storage->m_exceptions.front();
				m_storage->m_exceptions.pop_front();
				if(!(exception == nullptr))
					std::rethrow_exception(exception);
			}
		}

		/**
		 * Tests if this future sequence contains an (or more) exception.
		 */
		bool has_exception() const
		{
			read_lock lock(m_storage->m_mutex);
			return m_storage->m_exceptions.size() > 0;
		}

		/**
		 * Tests if this future sequence contains values and/or exceptions.
		 */
		bool isReady() const
		{
			return has_exception();
		}

		/**
		 * Blocks until this future is ready.
		 */
		void wait() const
		{
			while(!isReady())
				boost::this_thread::yield();
		}

		/**
		 * Blocks until this future is ready or until the given duration has been waited.
		 *
		 * @returns   true in case a value or an exception has been set
		 * @see       ready
		 */
		bool timed_wait(boost::posix_time::time_duration d)
		{
			boost::posix_time::ptime end_wait = boost::posix_time::microsec_clock::local_time() + d;
			while(!isReady())
			{
				if(boost::posix_time::microsec_clock::local_time() >= end_wait)
					return false;

				boost::this_thread::yield();
			}

			return true;
		}

		/**
		 * Adds the given exception to this future sequence.
		 */
		void add_exception(std::exception_ptr exception)
		{
			write_lock lock(m_storage->m_mutex);
			m_storage->m_exceptions.push_back(exception);
		}

	private:

		typedef std::list<std::exception_ptr>   exceptions;
		typedef boost::shared_mutex             mutex;
		typedef boost::shared_lock<mutex>       read_lock;
		typedef boost::unique_lock<mutex>       write_lock;

		struct storage
		{
			exceptions   m_exceptions;
			mutex        m_mutex;
		};

		boost::shared_ptr<storage>   m_storage;
	};
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
